#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
HTTP客户端核心类

该模块提供了HTTP客户端的核心功能，包括请求发送、响应处理、会话管理等。

创建时间: 2023-07-15
作者: Python Tools Team
"""

import json
import time
from enum import Enum
from typing import Dict, Any, Optional, Union, List, Tuple, Callable, Type, TypeVar

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

from modules.logger import LogManager
from modules.exception.exception_handler import exception_handler

from .exception import HttpClientException, RequestException, ResponseException, TimeoutException
from .retry_handler import RetryHandler

# 创建日志记录器
_logger = LogManager.get_both_logger(name="http_client")


class HttpMethod(Enum):
    """
    HTTP请求方法枚举
    """
    GET = "GET"
    POST = "POST"
    PUT = "PUT"
    DELETE = "DELETE"
    PATCH = "PATCH"
    HEAD = "HEAD"
    OPTIONS = "OPTIONS"


class HttpResponse:
    """
    HTTP响应封装类
    
    提供了对HTTP响应的统一封装，简化响应处理流程。
    """
    
    def __init__(self, response: requests.Response):
        """
        初始化HTTP响应对象
        
        Args:
            response: requests库的原始响应对象
        """
        self._response = response
        self._text = None
        self._json = None
    
    @property
    def status_code(self) -> int:
        """
        获取HTTP状态码
        
        Returns:
            int: HTTP状态码
        """
        return self._response.status_code
    
    @property
    def headers(self) -> Dict[str, str]:
        """
        获取响应头
        
        Returns:
            Dict[str, str]: 响应头字典
        """
        return dict(self._response.headers)
    
    @property
    def cookies(self) -> Dict[str, str]:
        """
        获取响应cookies
        
        Returns:
            Dict[str, str]: cookies字典
        """
        return dict(self._response.cookies)
    
    @property
    def url(self) -> str:
        """
        获取响应URL
        
        Returns:
            str: 响应URL
        """
        return self._response.url
    
    @property
    def text(self) -> str:
        """
        获取响应文本
        
        Returns:
            str: 响应文本内容
        """
        if self._text is None:
            self._text = self._response.text
        return self._text
    
    @property
    def content(self) -> bytes:
        """
        获取响应二进制内容
        
        Returns:
            bytes: 响应二进制内容
        """
        return self._response.content
    
    def json(self, **kwargs) -> Any:
        """
        将响应解析为JSON
        
        Args:
            **kwargs: 传递给json.loads的参数
            
        Returns:
            Any: 解析后的JSON对象
            
        Raises:
            ResponseException: 如果响应不是有效的JSON格式
        """
        if self._json is None:
            try:
                self._json = self._response.json(**kwargs)
            except ValueError as e:
                raise ResponseException(
                    message="响应不是有效的JSON格式",
                    code="E100201",
                    cause=e,
                    response=self._response
                )
        return self._json
    
    def raise_for_status(self) -> 'HttpResponse':
        """
        检查响应状态，如果是4xx或5xx则抛出异常
        
        Returns:
            HttpResponse: 当前响应对象（用于链式调用）
            
        Raises:
            ResponseException: 如果响应状态码是4xx或5xx
        """
        try:
            self._response.raise_for_status()
        except requests.HTTPError as e:
            raise ResponseException(
                message=f"HTTP错误: {self.status_code}",
                code="E100202",
                cause=e,
                response=self._response
            )
        return self
    
    def is_success(self) -> bool:
        """
        检查响应是否成功（状态码2xx）
        
        Returns:
            bool: 如果状态码是2xx则返回True，否则返回False
        """
        return 200 <= self.status_code < 300
    
    def is_redirect(self) -> bool:
        """
        检查响应是否是重定向（状态码3xx）
        
        Returns:
            bool: 如果状态码是3xx则返回True，否则返回False
        """
        return 300 <= self.status_code < 400
    
    def is_client_error(self) -> bool:
        """
        检查响应是否是客户端错误（状态码4xx）
        
        Returns:
            bool: 如果状态码是4xx则返回True，否则返回False
        """
        return 400 <= self.status_code < 500
    
    def is_server_error(self) -> bool:
        """
        检查响应是否是服务器错误（状态码5xx）
        
        Returns:
            bool: 如果状态码是5xx则返回True，否则返回False
        """
        return 500 <= self.status_code < 600
    
    def __repr__(self) -> str:
        return f"<HttpResponse [{self.status_code}]>"


class HttpClient:
    """
    HTTP客户端类
    
    提供了发送各种HTTP请求的方法，支持会话管理、自动重试、超时控制等功能。
    """
    
    def __init__(self, 
                 base_url: str = "", 
                 headers: Optional[Dict[str, str]] = None,
                 cookies: Optional[Dict[str, str]] = None,
                 timeout: Union[float, Tuple[float, float]] = (5.0, 30.0),
                 verify: bool = True,
                 cert: Optional[Union[str, Tuple[str, str]]] = None,
                 proxies: Optional[Dict[str, str]] = None,
                 retry_handler: Optional[RetryHandler] = None,
                 auth: Optional[Any] = None):
        """
        初始化HTTP客户端
        
        Args:
            base_url: 基础URL，所有请求都会基于此URL
            headers: 默认请求头
            cookies: 默认cookies
            timeout: 请求超时时间，可以是单个浮点数或(连接超时, 读取超时)的元组
            verify: 是否验证SSL证书
            cert: SSL客户端证书，可以是包含证书和密钥的元组
            proxies: 代理配置
            retry_handler: 重试处理器
            auth: 认证处理器
        """
        self.base_url = base_url.rstrip('/') if base_url else ""
        self.headers = headers or {}
        self.cookies = cookies or {}
        self.timeout = timeout
        self.verify = verify
        self.cert = cert
        self.proxies = proxies
        self.auth = auth
        
        # 创建会话
        self.session = requests.Session()
        
        # 配置重试
        if retry_handler:
            self._configure_retry(retry_handler)
    
    def _configure_retry(self, retry_handler: RetryHandler) -> None:
        """
        配置请求重试
        
        Args:
            retry_handler: 重试处理器
        """
        retry = Retry(
            total=retry_handler.max_retries,
            backoff_factor=retry_handler.backoff_factor,
            status_forcelist=retry_handler.status_codes,
            allowed_methods=retry_handler.methods,
            raise_on_status=False
        )
        
        adapter = HTTPAdapter(max_retries=retry)
        self.session.mount('http://', adapter)
        self.session.mount('https://', adapter)
    
    def _build_url(self, url: str) -> str:
        """
        构建完整的请求URL
        
        Args:
            url: 请求路径
            
        Returns:
            str: 完整的请求URL
        """
        if url.startswith(('http://', 'https://')):
            return url
        
        # 确保URL路径以/开头
        if not url.startswith('/'):
            url = '/' + url
            
        return self.base_url + url
    
    @exception_handler(
        exception_type=[requests.RequestException, json.JSONDecodeError],
        reraise=True,
        reraise_as=HttpClientException,
        log_level="ERROR",
        logger=_logger
    )
    def request(self, 
                method: Union[str, HttpMethod], 
                url: str, 
                params: Optional[Dict[str, Any]] = None,
                data: Optional[Any] = None,
                json_data: Optional[Dict[str, Any]] = None,
                headers: Optional[Dict[str, str]] = None,
                cookies: Optional[Dict[str, str]] = None,
                timeout: Optional[Union[float, Tuple[float, float]]] = None,
                verify: Optional[bool] = None,
                cert: Optional[Union[str, Tuple[str, str]]] = None,
                proxies: Optional[Dict[str, str]] = None,
                auth: Optional[Any] = None,
                allow_redirects: bool = True,
                stream: bool = False) -> HttpResponse:
        """
        发送HTTP请求
        
        Args:
            method: HTTP方法
            url: 请求URL
            params: URL查询参数
            data: 请求体数据
            json_data: JSON格式的请求体数据
            headers: 请求头
            cookies: 请求cookies
            timeout: 请求超时时间
            verify: 是否验证SSL证书
            cert: SSL客户端证书
            proxies: 代理配置
            auth: 认证处理器
            allow_redirects: 是否允许重定向
            stream: 是否使用流式传输
            
        Returns:
            HttpResponse: HTTP响应对象
            
        Raises:
            RequestException: 请求构建或发送过程中的错误
            ResponseException: 响应处理过程中的错误
            TimeoutException: 请求超时
            HttpClientException: 其他HTTP客户端错误
        """
        # 处理HTTP方法
        if isinstance(method, HttpMethod):
            method = method.value
        
        # 构建完整URL
        full_url = self._build_url(url)
        
        # 合并请求头和cookies
        merged_headers = {**self.headers, **(headers or {})}
        merged_cookies = {**self.cookies, **(cookies or {})}
        
        # 设置请求参数
        request_kwargs = {
            'params': params,
            'headers': merged_headers,
            'cookies': merged_cookies,
            'timeout': timeout or self.timeout,
            'verify': self.verify if verify is None else verify,
            'cert': cert or self.cert,
            'proxies': proxies or self.proxies,
            'auth': auth or self.auth,
            'allow_redirects': allow_redirects,
            'stream': stream
        }
        
        # 设置请求体
        if json_data is not None:
            request_kwargs['json'] = json_data
        elif data is not None:
            request_kwargs['data'] = data
        
        # 记录请求信息
        _logger.debug(f"发送 {method} 请求到 {full_url}")
        start_time = time.time()
        
        try:
            # 发送请求
            response = self.session.request(method, full_url, **request_kwargs)
            elapsed = time.time() - start_time
            
            # 记录响应信息
            _logger.debug(f"收到来自 {full_url} 的响应: {response.status_code}, 耗时: {elapsed:.3f}秒")
            
            return HttpResponse(response)
            
        except requests.Timeout as e:
            elapsed = time.time() - start_time
            _logger.error(f"请求 {full_url} 超时, 耗时: {elapsed:.3f}秒")
            raise TimeoutException(
                message=f"请求超时: {full_url}",
                cause=e,
                timeout=timeout or self.timeout
            )
            
        except requests.RequestException as e:
            elapsed = time.time() - start_time
            _logger.error(f"请求 {full_url} 失败, 耗时: {elapsed:.3f}秒, 错误: {str(e)}")
            raise RequestException(
                message=f"请求失败: {full_url}",
                cause=e,
                request=e.request if hasattr(e, 'request') else None
            )
    
    def get(self, url: str, **kwargs) -> HttpResponse:
        """
        发送GET请求
        
        Args:
            url: 请求URL
            **kwargs: 其他请求参数
            
        Returns:
            HttpResponse: HTTP响应对象
        """
        return self.request(HttpMethod.GET, url, **kwargs)
    
    def post(self, url: str, **kwargs) -> HttpResponse:
        """
        发送POST请求
        
        Args:
            url: 请求URL
            **kwargs: 其他请求参数
            
        Returns:
            HttpResponse: HTTP响应对象
        """
        return self.request(HttpMethod.POST, url, **kwargs)
    
    def put(self, url: str, **kwargs) -> HttpResponse:
        """
        发送PUT请求
        
        Args:
            url: 请求URL
            **kwargs: 其他请求参数
            
        Returns:
            HttpResponse: HTTP响应对象
        """
        return self.request(HttpMethod.PUT, url, **kwargs)
    
    def delete(self, url: str, **kwargs) -> HttpResponse:
        """
        发送DELETE请求
        
        Args:
            url: 请求URL
            **kwargs: 其他请求参数
            
        Returns:
            HttpResponse: HTTP响应对象
        """
        return self.request(HttpMethod.DELETE, url, **kwargs)
    
    def patch(self, url: str, **kwargs) -> HttpResponse:
        """
        发送PATCH请求
        
        Args:
            url: 请求URL
            **kwargs: 其他请求参数
            
        Returns:
            HttpResponse: HTTP响应对象
        """
        return self.request(HttpMethod.PATCH, url, **kwargs)
    
    def head(self, url: str, **kwargs) -> HttpResponse:
        """
        发送HEAD请求
        
        Args:
            url: 请求URL
            **kwargs: 其他请求参数
            
        Returns:
            HttpResponse: HTTP响应对象
        """
        return self.request(HttpMethod.HEAD, url, **kwargs)
    
    def options(self, url: str, **kwargs) -> HttpResponse:
        """
        发送OPTIONS请求
        
        Args:
            url: 请求URL
            **kwargs: 其他请求参数
            
        Returns:
            HttpResponse: HTTP响应对象
        """
        return self.request(HttpMethod.OPTIONS, url, **kwargs)
    
    def download_file(self, url: str, file_path: str, chunk_size: int = 8192, **kwargs) -> HttpResponse:
        """
        下载文件
        
        Args:
            url: 文件URL
            file_path: 保存文件的路径
            chunk_size: 每次读取的块大小
            **kwargs: 其他请求参数
            
        Returns:
            HttpResponse: HTTP响应对象
        """
        kwargs['stream'] = True
        response = self.get(url, **kwargs)
        
        with open(file_path, 'wb') as f:
            for chunk in response._response.iter_content(chunk_size=chunk_size):
                if chunk:
                    f.write(chunk)
        
        return response
    
    def upload_file(self, url: str, file_path: str, file_param_name: str = 'file', **kwargs) -> HttpResponse:
        """
        上传文件
        
        Args:
            url: 上传URL
            file_path: 要上传的文件路径
            file_param_name: 文件参数名
            **kwargs: 其他请求参数
            
        Returns:
            HttpResponse: HTTP响应对象
        """
        with open(file_path, 'rb') as f:
            files = {file_param_name: f}
            return self.post(url, files=files, **kwargs)
    
    def close(self) -> None:
        """
        关闭会话
        """
        self.session.close()
    
    def __enter__(self):
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()